#ifndef _QKD_5G_EV_QUEUE_H_
#define _QKD_5G_EV_QUEUE_H_

#include <queue>
#include <condition_variable>
#include <mutex>
#include <iostream>
#include <atomic>
#include <any>
#include <map>
#include <string>

template <typename T>
class BlockQueue
{
public:
    BlockQueue<T>() : mShutdown(false) { }

    void Push(const T& value)
    {
        std::lock_guard<std::mutex> lock(mMtx);
        mQue.push(std::move(value));

        mCv.notify_one();
    }

    bool Empty()
    {
        std::lock_guard<std::mutex> lock(mMtx);

        return mQue.empty();
    }

    T Pop()
    {
        std::unique_lock<std::mutex> lock(mMtx);
        mCv.wait(lock, [&] {return !mQue.empty() || mShutdown; });

        if (mQue.empty() || mShutdown)
            return T();

        auto value = mQue.front();

        mQue.pop();

        return value;
    }

    void Cancel()
    {
        std::unique_lock<std::mutex> lock(mMtx);

        while (!mQue.empty()) {
            T& value = mQue.front();

            DeleteQueuedObject(value);

            mQue.pop();
        }

        mShutdown = true;

        mCv.notify_all();
    }

    void Clean()
    {
        std::unique_lock<std::mutex> lock(mMtx);
        while (!mQue.empty()) {
            auto value = mQue.front();
            mQue.pop();
            DeleteQueuedObject(value);
        }

    }

private:
    template<typename E = T>
    typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; }

    template<typename E = T>
    typename std::enable_if < !std::is_pointer<E>::value >::type DeleteQueuedObject(E const& /*packet*/) { }

private:
    std::mutex mMtx;
    std::queue<T> mQue;
    std::condition_variable mCv;
    std::atomic<bool> mShutdown;
};

class QueueRepo
{
public:
    static QueueRepo* Repo()
    {
        static QueueRepo repo;

        return &repo;
    }

    template<typename T>
    BlockQueue<T>* Queue(const std::string& id)
    {
        auto any_que = mQues.find(id);
        if (any_que != mQues.end())
            return static_cast<BlockQueue<T>*>(any_que->second);

        auto que = new BlockQueue<T>();
        mQues.insert(std::make_pair(id, que));

        return que;
    }

private:
    std::map<std::string, void*> mQues;
};

#endif
